<?php

#namespace Giocondo;

# widget -- A windowing object.
abstract class Widget {
	# Default options initialization.
	protected $options = array();

	# Default key when a single configuration value is given.
	# TODO: when upgrading to PHP 5.3.0 use a more appropriate class constant
	# and reference it with static::default_option (late binding).
	#const default_option = NULL;
	public $default_option = NULL;

	# Widget unique id.
	protected $id;

	# There can only be one parent. This allows checking for that.
	protected $has_parent = FALSE;

	public function __construct($options = array()) {
		global $Giocondo_next_id, $Giocondo_wid_idx, $Giocondo_js, 
			$Giocondo_new_wid;

		$this->id = $Giocondo_next_id++;
		$Giocondo_wid_idx[$this->id] = $this;
		$Giocondo_js[$this->id] = array();
		$Giocondo_new_wid[$this->id] = $this;

		# The second boolean option tells configure that it has been called from 
		# within the constructor, so that options that can be setted only at 
		# widget creation time can be configured without raising an error.
		$this->configure($options, FALSE);
	}

	# To be displayed every widget must be a direct
	# or indirect child of a top level window.
	public function display($options=array()) {
		global $Giocondo_win_js, $Giocondo_js, $Giocondo_new_wid;

		# Allows to specify a single option without associated value as a string.
		if (! is_array($options)) {
			$options = array($options);
		}

		foreach ($options as $option => $value) {
			# Allows to specify options without values as values.
			if (is_int($option)) {
				$option = $value;
				$value = '';
			}

			if ($value != '') {
				# Attach hidden event or other attribute not configurable by HTML.
				$Giocondo_win_js .= $value;
			}
			if (isset($Giocondo_js[$this->id][$option])) {
				$Giocondo_win_js .= $Giocondo_js[$this->id][$option];
			}
		}
		unset($Giocondo_js[$this->id], $Giocondo_new_wid[$this->id]);
	}

	public function cget($option) {
		return $this->options[$option];
	}

	public function chas($option) {
		return isset($this->options[$option]);
	}

	public function configure($options = array(), $after_creation = TRUE) {
		# static::default_option
		if (is_null($this->default_option) || is_array($options)) {
			foreach ($options as $option => $value) {
				$this->cset($option, $value, $after_creation);
			}
		} else {
			# static::default_option
			$this->cset($this->default_option, $options, $after_creation);
		}
	}

	protected function cset($option, $value, $after_creation = TRUE) {
		global $Giocondo_js;

		# When a widget is going to be displayed configuration should take place 
		# statically within generated HTML, except for that options not 
		# configurable statically. Only these will be configured by generating 
		# appropriate JavaScript code. In event handling mode when reconfiguring an 
		# alredy displayed widget (both visible or not) all options must be 
		# configured using JavaScript code.
		#
		# Using only JavaScript in both mode will work, but we also want to make 
		# use of HTML features: maybe it should speed up things for browser with 
		# slow JavaScript implementation.
		#
		# An exception are attributes that take JavaScript code as values (e.g.
		# onclick and ontextinput): they are always configured dinamically to avoid
		# dereferencing the global Giocondo JavaScript object many times (this is
		# referenced only one time with a "with" JavaScript construct).

		# Put most common attributes first to optimize.
		switch ($option) {
			case 'onClicked':
				# Button
				if ($value === '') {
					$Giocondo_js[$this->id][$option] = sprintf('n(%u,0);', $this->id);
				} else {
					$Giocondo_js[$this->id][$option] = sprintf('e(%u,0);', $this->id);
				}
			break;

			case 'text':
				# Button, Label
				$Giocondo_js[$this->id][$option] = sprintf("t(%u,'%s');", $this->id, String::filter($value, 'pj'));
			break;
		}

		$this->options[$option] = $value;
	}

	# This and other functions named after an event (which call onEvent in 
	# descendent widgets) don't execute the command, but return the command ready 
	# to be executed by the client code. Users must use eval() on the result of 
	# this function. We can't evaluate the command inside the function because it 
	# may refer to global variables which in PHP aren't inherited in the local 
	# scope as happens e.g. in the C language.
	protected function onEvent($event, $subst = array()) {
		if (($command = rtrim($this->options[$event])) != '') {
			if ($command[strlen($command)-1] != ';') {
				$command .= ';';
			}
			# Allows to specify a single percent string to substitute as a string.
			if (! is_array($subst)) {
				$subst = array($subst);
			}

			# Widget percent string substitution.
			$command=str_replace('%w', sprintf('$Giocondo_wid_idx[%u]', $this->id), $command);
			foreach ($subst as $var => $value) {
				switch ($var) {
				  case 'u':
					case 'v':
						# Value/url percent string substitution.
						$command=str_replace("%$var",
							sprintf("'%s'", addcslashes($value, "'\\")), $command);
					break;

					# No error checking.
				}
			}
		}
		return $command;
	}

	# This method provides an uniform way for Ajax code acting in the global name 
	# space to update the value of the widget in response to client generated 
	# onChanged events.
	# Should be considered a friend of Event.php and never be called by user code, 
	# despite the fact that it is public.
	# Default implementation return TRUE that means that the widget doesn't 
	# respond to a (psedo or effective) onChanged event. 
	# Descendend class should return FALSE when overriding.
	public function setValue($value) {
		return TRUE;
	}

	# TODO: it should be called only when a widget is explicity destroyed.
	public function __destructor() {
		Error::Trigger('destructor of ' . $this->id . ' called');
  }

	# TODO: implement
	# What if the widget has a parent?
	public function delete() {
		global $Giocondo_wid_idx, $Giocondo_js, $Giocondo_new_wid;

		unset($Giocondo_wid_idx[$this->id]);
		unset($Giocondo_new_wid[$this->id]);

		# TODO: will = suffice here?
		@$Giocondo_js[$this->id]['onDelete'] .= sprintf('f(%u);', $this->id);
	}
}

# End of file
# vim: set ts=2 sw=2 noet:
?>
